{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Project Solution: Goal 1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Our starting point is where we left off at the end of Project 1.\n",
    "\n",
    "Our first goal is to rewrite all properties as lazy properties."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here is the code we ended up with in Project 1:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import math\n",
    "\n",
    "class Polygon:\n",
    "    def __init__(self, n, R):\n",
    "        if n < 3:\n",
    "            raise ValueError('Polygon must have at least 3 vertices.')\n",
    "        self._n = n\n",
    "        self._R = R\n",
    "        \n",
    "    def __repr__(self):\n",
    "        return f'Polygon(n={self._n}, R={self._R})'\n",
    "    \n",
    "    @property\n",
    "    def count_vertices(self):\n",
    "        return self._n\n",
    "    \n",
    "    @property\n",
    "    def count_edges(self):\n",
    "        return self._n\n",
    "    \n",
    "    @property\n",
    "    def circumradius(self):\n",
    "        return self._R\n",
    "    \n",
    "    @property\n",
    "    def interior_angle(self):\n",
    "        return (self._n - 2) * 180 / self._n\n",
    "\n",
    "    @property\n",
    "    def side_length(self):\n",
    "        return 2 * self._R * math.sin(math.pi / self._n)\n",
    "    \n",
    "    @property\n",
    "    def apothem(self):\n",
    "        return self._R * math.cos(math.pi / self._n)\n",
    "    \n",
    "    @property\n",
    "    def area(self):\n",
    "        return self._n / 2 * self.side_length * self.apothem\n",
    "    \n",
    "    @property\n",
    "    def perimeter(self):\n",
    "        return self._n * self.side_length\n",
    "    \n",
    "    def __eq__(self, other):\n",
    "        if isinstance(other, self.__class__):\n",
    "            return (self.count_edges == other.count_edges \n",
    "                    and self.circumradius == other.circumradius)\n",
    "        else:\n",
    "            return NotImplemented\n",
    "        \n",
    "    def __gt__(self, other):\n",
    "        if isinstance(other, self.__class__):\n",
    "            return self.count_vertices > other.count_vertices\n",
    "        else:\n",
    "            return NotImplemented\n",
    "            "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's rewrite computed properties as lazy properties.\n",
    "\n",
    "To do that we need to do two things:\n",
    "\n",
    "* create a private backing variable for the property\n",
    "* compute the property if the backing variable is None and store the result into the backing variable"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class Polygon:\n",
    "    def __init__(self, n, R):\n",
    "        if n < 3:\n",
    "            raise ValueError('Polygon must have at least 3 vertices.')\n",
    "        self._n = n\n",
    "        self._R = R\n",
    "        \n",
    "        self._interior_angle = None\n",
    "        self._side_length = None\n",
    "        self._apothem = None\n",
    "        self._area = None\n",
    "        self._perimeter = None\n",
    "        \n",
    "    def __repr__(self):\n",
    "        return f'Polygon(n={self._n}, R={self._R})'\n",
    "    \n",
    "    @property\n",
    "    def count_vertices(self):\n",
    "        return self._n\n",
    "    \n",
    "    @property\n",
    "    def count_edges(self):\n",
    "        return self._n\n",
    "    \n",
    "    @property\n",
    "    def circumradius(self):\n",
    "        return self._R\n",
    "    \n",
    "    @property\n",
    "    def interior_angle(self):\n",
    "        if self._interior_angle is None:\n",
    "            self._interior_angle = (self._n - 2) * 180 / self._n\n",
    "        return self._interior_angle\n",
    "\n",
    "    @property\n",
    "    def side_length(self):\n",
    "        if self._side_length is None:\n",
    "            self._side_length = 2 * self._R * math.sin(math.pi / self._n)\n",
    "        return self._side_length\n",
    "    \n",
    "    @property\n",
    "    def apothem(self):\n",
    "        if self._apothem is None:\n",
    "            self._apothem = self._R * math.cos(math.pi / self._n)\n",
    "        return self._apothem\n",
    "    \n",
    "    @property\n",
    "    def area(self):\n",
    "        if self._area is None:\n",
    "            self._area = self._n / 2 * self.side_length * self.apothem\n",
    "        return self._area\n",
    "    \n",
    "    @property\n",
    "    def perimeter(self):\n",
    "        if self._perimeter is None:\n",
    "            self._perimeter = self._n * self.side_length\n",
    "        return self._perimeter\n",
    "    \n",
    "    def __eq__(self, other):\n",
    "        if isinstance(other, self.__class__):\n",
    "            return (self.count_edges == other.count_edges \n",
    "                    and self.circumradius == other.circumradius)\n",
    "        else:\n",
    "            return NotImplemented\n",
    "        \n",
    "    def __gt__(self, other):\n",
    "        if isinstance(other, self.__class__):\n",
    "            return self.count_vertices > other.count_vertices\n",
    "        else:\n",
    "            return NotImplemented"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And let's run the same unit test we wrote for Project 1:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def test_polygon():\n",
    "    abs_tol = 0.001\n",
    "    rel_tol = 0.001\n",
    "    \n",
    "    try:\n",
    "        p = Polygon(2, 10)\n",
    "        assert False, ('Creating a Polygon with 2 sides: '\n",
    "                       ' Exception expected, not received')\n",
    "    except ValueError:\n",
    "        pass\n",
    "                       \n",
    "    n = 3\n",
    "    R = 1\n",
    "    p = Polygon(n, R)\n",
    "    assert str(p) == 'Polygon(n=3, R=1)', f'actual: {str(p)}'\n",
    "    assert p.count_vertices == n, (f'actual: {p.count_vertices},'\n",
    "                                   f' expected: {n}')\n",
    "    assert p.count_edges == n, f'actual: {p.count_edges}, expected: {n}'\n",
    "    assert p.circumradius == R, f'actual: {p.circumradius}, expected: {n}'\n",
    "    assert p.interior_angle == 60, (f'actual: {p.interior_angle},'\n",
    "                                    ' expected: 60')\n",
    "    n = 4\n",
    "    R = 1\n",
    "    p = Polygon(n, R)\n",
    "    assert p.interior_angle == 90, (f'actual: {p.interior_angle}, '\n",
    "                                    ' expected: 90')\n",
    "    assert math.isclose(p.area, 2, \n",
    "                        rel_tol=abs_tol, \n",
    "                        abs_tol=abs_tol), (f'actual: {p.area},'\n",
    "                                           ' expected: 2.0')\n",
    "    \n",
    "    assert math.isclose(p.side_length, math.sqrt(2),\n",
    "                       rel_tol=rel_tol,\n",
    "                       abs_tol=abs_tol), (f'actual: {p.side_length},'\n",
    "                                          f' expected: {math.sqrt(2)}')\n",
    "    \n",
    "    assert math.isclose(p.perimeter, 4 * math.sqrt(2),\n",
    "                       rel_tol=rel_tol,\n",
    "                       abs_tol=abs_tol), (f'actual: {p.perimeter},'\n",
    "                                          f' expected: {4 * math.sqrt(2)}')\n",
    "    \n",
    "    assert math.isclose(p.apothem, 0.707,\n",
    "                       rel_tol=rel_tol,\n",
    "                       abs_tol=abs_tol), (f'actual: {p.perimeter},'\n",
    "                                          ' expected: 0.707')\n",
    "    p = Polygon(6, 2)\n",
    "    assert math.isclose(p.side_length, 2,\n",
    "                        rel_tol=rel_tol, abs_tol=abs_tol)\n",
    "    assert math.isclose(p.apothem, 1.73205,\n",
    "                        rel_tol=rel_tol, abs_tol=abs_tol)\n",
    "    assert math.isclose(p.area, 10.3923,\n",
    "                        rel_tol=rel_tol, abs_tol=abs_tol)\n",
    "    assert math.isclose(p.perimeter, 12,\n",
    "                        rel_tol=rel_tol, abs_tol=abs_tol)\n",
    "    assert math.isclose(p.interior_angle, 120,\n",
    "                        rel_tol=rel_tol, abs_tol=abs_tol)\n",
    "    \n",
    "    p = Polygon(12, 3)\n",
    "    assert math.isclose(p.side_length, 1.55291,\n",
    "                        rel_tol=rel_tol, abs_tol=abs_tol)\n",
    "    assert math.isclose(p.apothem, 2.89778,\n",
    "                        rel_tol=rel_tol, abs_tol=abs_tol)\n",
    "    assert math.isclose(p.area, 27,\n",
    "                        rel_tol=rel_tol, abs_tol=abs_tol)\n",
    "    assert math.isclose(p.perimeter, 18.635,\n",
    "                        rel_tol=rel_tol, abs_tol=abs_tol)\n",
    "    assert math.isclose(p.interior_angle, 150,\n",
    "                        rel_tol=rel_tol, abs_tol=abs_tol)\n",
    "    \n",
    "    p1 = Polygon(3, 10)\n",
    "    p2 = Polygon(10, 10)\n",
    "    p3 = Polygon(15, 10)\n",
    "    p4 = Polygon(15, 100)\n",
    "    p5 = Polygon(15, 100)\n",
    "    \n",
    "    assert p2 > p1\n",
    "    assert p2 < p3\n",
    "    assert p3 != p4\n",
    "    assert p1 != p4\n",
    "    assert p4 == p5"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "test_polygon()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "OK, looks like this goal is complete."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
